QueryMethodValidator.java
package org.codefilarete.stalactite.spring.repository.query;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParameterOutOfBoundsException;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.util.Streamable;
/**
* Validator for query method. Basically, it ensures that arguments match length and types of properties.
*
* @author Guillaume Mary
* @see #validate()
*/
public class QueryMethodValidator {
private final PartTree tree;
private final QueryMethod method;
public QueryMethodValidator(PartTree tree, QueryMethod method) {
this.tree = tree;
this.method = method;
}
public void validate() {
int argCount = 0;
Iterable<Part> parts = () -> tree.stream().flatMap(Streamable::stream).iterator();
for (Part part : parts) {
int numberOfArguments = part.getNumberOfArguments();
for (int i = 0; i < numberOfArguments; i++) {
throwExceptionOnArgumentMismatch(part, argCount);
argCount++;
}
}
}
private void throwExceptionOnArgumentMismatch(Part part, int index) {
Type type = part.getType();
String property = part.getProperty().toDotPath();
Parameter parameter;
try {
parameter = method.getParameters().getBindableParameter(index);
} catch (ParameterOutOfBoundsException e) {
throw new IllegalStateException(String.format(
"Method %s expects at least %d arguments but only found %d. This leaves an operator of type %s for property %s unbound.",
method.getName(), index + 1, index, type.name(), property));
}
if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) {
throw new IllegalStateException(wrongParameterTypeMessage(property, type, "Collection", parameter));
} else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) {
throw new IllegalStateException(wrongParameterTypeMessage(property, type, "scalar", parameter));
}
}
private String wrongParameterTypeMessage(String property, Type operatorType, String expectedArgumentType, Parameter parameter) {
return String.format("Operator %s on %s requires a %s argument, found %s in method %s.", operatorType.name(),
property, expectedArgumentType, parameter.getType(), method.getName());
}
private boolean parameterIsCollectionLike(Parameter parameter) {
return Iterable.class.isAssignableFrom(parameter.getType()) || parameter.getType().isArray();
}
/**
* Arrays are may be treated as collection like or in the case of binary data as scalar
*/
private boolean parameterIsScalarLike(Parameter parameter) {
return !Iterable.class.isAssignableFrom(parameter.getType());
}
private boolean expectsCollection(Type type) {
return type == Type.IN || type == Type.NOT_IN;
}
}